package buildSchema

import (
	"bytes"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"text/template"
	"time"
	"tupu-go/config"
	"tupu-go/controller/nebula/dto"
	"tupu-go/pkg/database"
)

var typeMap = map[string]string{
	"string":    "string",
	"double":    "double",
	"int":       "int64",
	"timestamp": "timestamp",
}

func BuildTagDDL(v dto.Schema) []string {
	var ddls []string
	for _, tag := range v.VertexTypes {
		buf := bytes.NewBufferString("CREATE TAG IF NOT EXISTS `" + tag.Name + "` (")
		var cols []string
		for _, p := range tag.Properties {
			nebulaType := typeMap[p.Type]
			if !p.Nullable {
				nebulaType += " NOT NULL"
			}
			cols = append(cols, fmt.Sprintf("`%s` %s COMMENT '%s'", p.Name, nebulaType, p.Comment))
		}
		buf.WriteString(strings.Join(cols, ","))
		buf.WriteString(");")
		ddls = append(ddls, buf.String())
	}
	return ddls
}

func BuildEdgeDDL(e dto.Schema) []string {
	var ddls []string
	for _, edge := range e.EdgeTypes {
		buf := bytes.NewBufferString("CREATE EDGE IF NOT EXISTS `" + edge.Name + "` (")
		var cols []string
		for _, p := range edge.Properties {
			nebulaType := typeMap[p.Type]
			cols = append(cols, fmt.Sprintf("`%s` %s COMMENT '%s'", p.Name, nebulaType, p.Comment))
		}
		buf.WriteString(strings.Join(cols, ","))
		buf.WriteString(");")
		ddls = append(ddls, buf.String())
	}
	return ddls
}

func CreateSchema(schema dto.Schema) error {
	db := database.GetNDB()

	var errs []error

	// 1) 建 TAG
	for _, ddl := range BuildTagDDL(schema) {
		if err := db.Raw(strings.TrimSuffix(ddl, ";")).Exec(); err != nil {
			errs = append(errs, fmt.Errorf("create tag failed: %w, sql=%s", err, ddl))
		}
	}

	// 2) 建 EDGE
	for _, ddl := range BuildEdgeDDL(schema) {
		if err := db.Raw(strings.TrimSuffix(ddl, ";")).Exec(); err != nil {
			errs = append(errs, fmt.Errorf("create edge failed: %w, sql=%s", err, ddl))
		}
	}

	if len(errs) == 0 {
		return nil
	}

	return errors.Join(errs...)
}

func GenerateYAMLContent(s dto.Schema) (string, error) {
	var (
		space    = config.Global.NebulaConfig.SpaceName
		user     = config.Global.NebulaConfig.User
		password = config.Global.NebulaConfig.Password
		address  = fmt.Sprintf("%s:%d", config.Global.NebulaConfig.Host, config.Global.NebulaConfig.Port)
	)

	type renderData struct {
		Space       string
		User        string
		Password    string
		Address     string
		DATE        string
		VertexTypes []struct {
			Name       string `json:"name"`
			Properties []struct {
				Name     string `json:"name"`
				Type     string `json:"type"`
				Nullable bool   `json:"nullable"`
				Comment  string `json:"comment"`
			} `json:"properties"`
			PrimaryKey string `json:"primary_key"`
		}
		EdgeTypes []struct {
			Name       string `json:"name"`
			Properties []struct {
				Name    string `json:"name"`
				Type    string `json:"type"`
				Comment string `json:"comment"`
			} `json:"properties"`
			SourceVertex string       `json:"source_vertex"`
			TargetVertex string       `json:"target_vertex"`
			Direction    string       `json:"direction"`
			Rand         dto.RandConf `json:"rand"`
			RandIndex    int          `json:"rand_index"`
		}
	}
	date := time.Now().Format("20060102150405")
	data := renderData{
		Space:       space,
		User:        user,
		Password:    password,
		Address:     address,
		DATE:        date,
		VertexTypes: s.VertexTypes,
		EdgeTypes:   s.EdgeTypes,
	}

	// YAML模板
	tpl := `clientSettings:
  retry: 3
  concurrency: 8
  channelBufferSize: 1024
  space: {{.Space}}
  connection:
    user: {{.User}}
    password: {{.Password}}
    address: {{.Address}}
  postStart:
    commands: |
      USE {{.Space}};

logPath: /tmp/nebula_importer_{{.DATE}}.log

files:
{{- range .VertexTypes}}
  - path: /data/csv/{{$.DATE}}/{{.Name}}_{{$.DATE}}.csv
    failDataPath: /tmp/fail/{{$.DATE}}/{{.Name}}_fail.csv
    batchSize: 2000
    type: vertex
    vertex:
      vid:
        index: 0
        type: string
      tags:
        - name: {{.Name}}
          props:
          {{- range $i, $p := .Properties}}
            - name: {{$p.Name}}
              index: {{$i}}
          {{- end}}
{{- end}}

{{- range .EdgeTypes}}
  {{- $randIdx := len .Properties}}
  {{- if and .Rand.Enable (eq .Rand.Algorithm "from") }}
    {{- $randIdx = indexOf .Properties .Rand.Params.from }}
  {{- else if .Rand.Enable }}
    {{- $randIdx = len .Properties}}
  {{- end}}
  - path: /data/csv/{{$.DATE}}/{{.Name}}_{{$.DATE}}.csv
    failDataPath: /tmp/fail/{{$.DATE}}/{{.Name}}_fail.csv
    batchSize: 2000
    type: edge
    edge:
      name: {{.Name}}
      srcVID:
        index: 0
        type: string
      dstVID:
        index: 1
        type: string
      props:
      {{- range $i, $p := .Properties}}
        - name: {{$p.Name}}
          index: {{add $i 2}}
          {{- if or (eq $p.Type "timestamp") (eq $p.Type "double") }}
          type: {{$p.Type}}
          {{- end}}
      {{- end}}
      {{- if and .Rand.Enable (ne .Rand.Algorithm "from") }}
        - name: rand
          index: {{add (len .Properties) 2}}
          type: int
      {{- end}}
{{- end}}
`

	funcMap := template.FuncMap{
		"add": func(a, b int) int { return a + b },
		"ne":  func(a, b string) bool { return a != b },
		"indexOf": func(props []struct{ Name string }, name string) int {
			for i, p := range props {
				if p.Name == name {
					return i + 2
				}
			}
			return len(props) + 2
		},
	}

	t, err := template.New("yaml").Funcs(funcMap).Parse(tpl)
	if err != nil {
		return "", fmt.Errorf("failed to parse template: %w", err)
	}

	var buf bytes.Buffer
	err = t.Execute(&buf, data)
	if err != nil {
		return "", fmt.Errorf("failed to execute template: %w", err)
	}

	return buf.String(), nil
}

// ------------------ 算法注册表 ------------------
type RandFunc func(row []string, params map[string]interface{}) int64
type RandRegistry map[string]RandFunc

var registry = RandRegistry{
	"hash":      randHash,
	"uuid":      randUUID,
	"from":      randFromCol,
	"fixed":     randFixed,
	"snowflake": randSnowflake,
	"auto":      randAuto,
}

// randHash 使用哈希函数生成VID，对应nebula-importer的hash函数
func randHash(row []string, params map[string]interface{}) int64 {
	// 获取要哈希的列索引或值
	if colName, ok := params["from"].(string); ok {
		// 从指定列获取值进行哈希
		for i, val := range row {
			if fmt.Sprintf("col_%d", i) == colName {
				return int64(hash(val))
			}
		}
	}
	// 默认使用第一列的值
	if len(row) > 0 {
		return int64(hash(row[0]))
	}
	return 0
}

// randUUID 生成UUID类型的VID，对应nebula-importer的uuid函数
func randUUID(row []string, params map[string]interface{}) int64 {
	// 获取要生成UUID的列索引或值
	if colName, ok := params["from"].(string); ok {
		// 从指定列获取值生成UUID
		for i, val := range row {
			if fmt.Sprintf("col_%d", i) == colName {
				return int64(generateUUID(val))
			}
		}
	}
	// 默认使用第一列的值
	if len(row) > 0 {
		return int64(generateUUID(row[0]))
	}
	return 0
}

// randFromCol 直接从指定列获取值作为VID
func randFromCol(row []string, params map[string]interface{}) int64 {
	if colName, ok := params["from"].(string); ok {
		// 查找指定列
		for i, val := range row {
			if fmt.Sprintf("col_%d", i) == colName {
				// 尝试转换为int64
				if intVal, err := strconv.ParseInt(val, 10, 64); err == nil {
					return intVal
				}
				// 如果不是数字，使用哈希
				return int64(hash(val))
			}
		}
	}
	return 0
}

// randFixed 返回固定值
func randFixed(row []string, params map[string]interface{}) int64 {
	if fixedVal, ok := params["value"].(int64); ok {
		return fixedVal
	}
	if fixedVal, ok := params["value"].(float64); ok {
		return int64(fixedVal)
	}
	return 1 // 默认固定值
}

// randSnowflake 生成雪花ID
func randSnowflake(row []string, params map[string]interface{}) int64 {
	// 简化的雪花算法实现
	timestamp := time.Now().UnixNano() / 1000000 // 毫秒时间戳
	machineID := int64(1)                        // 机器ID，可以从params获取
	if mid, ok := params["machine_id"].(float64); ok {
		machineID = int64(mid)
	}
	sequence := int64(0) // 序列号，实际应用中需要维护

	// 雪花ID格式：时间戳(41位) + 机器ID(10位) + 序列号(12位)
	return (timestamp << 22) | (machineID << 12) | sequence
}

// randAuto 自动选择合适的算法
func randAuto(row []string, params map[string]interface{}) int64 {
	// 默认使用哈希算法
	return randHash(row, params)
}

// 辅助函数：简单哈希实现
func hash(s string) uint32 {
	h := uint32(0)
	for _, c := range s {
		h = h*31 + uint32(c)
	}
	return h
}

// 辅助函数：简单UUID生成实现
func generateUUID(s string) uint64 {
	// 基于输入字符串生成确定性的UUID
	h := uint64(0)
	for _, c := range s {
		h = h*31 + uint64(c)
	}
	return h
}
